package yoyu.downloader;

/**
 * Created by zf617 on 2016/10/9.
 */
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;

import android.text.TextUtils;
import android.util.Log;

import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * 文件下载类
 *
 * @author yangxiaolong
 * @2014-5-6
 */
public class FileDownloadThread extends Thread {

    private static final String TAG = FileDownloadThread.class.getSimpleName();

    /** 当前下载是否完成 */
    private boolean isCompleted = false;
    /** 当前下载文件长度 */
    private long downloadLength = 0;
    /** 文件保存路径 */
    private File file;
    /** 文件下载路径 */
    private URL downloadUrl;
    /** 当前下载线程ID */
    private int threadId;
    /** 线程下载数据长度 */
    private long blockSize;

    private static final int TOTAL_VALUE_IN_CHUNKED_RESOURCE = -1;

    private long totalSize;

    /**
     *
     * @param downloadUrl:文件下载地址
     * @param file:文件保存路径
     * @param blocksize:下载数据长度
     * @param threadId:线程ID
     */
    public FileDownloadThread(URL downloadUrl, File file, long blocksize,
                              int threadId,long totalSize) {
        this.downloadUrl = downloadUrl;
        this.file = file;
        this.threadId = threadId;
        this.blockSize = blocksize;
        this.totalSize = totalSize;
    }

    @Override
    public void run() {

//        BufferedInputStream bis = null;
//        RandomAccessFile raf = null;
//
//        try {
//            URLConnection conn = downloadUrl.openConnection();
//            conn.setAllowUserInteraction(true);
//
            long startPos = blockSize * (threadId - 1);//开始位置
            long endPos = blockSize * threadId - 1;//结束位置
//            //设置当前线程下载的起点、终点
//            conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
//            System.out.println(Thread.currentThread().getName() + "  bytes="
//                    + startPos + "-" + endPos);
//
//            byte[] buffer = new byte[1024];
//            bis = new BufferedInputStream(conn.getInputStream());
//
//            raf = new RandomAccessFile(file, "rwd");
//            raf.seek(startPos);
//            int len;
//            while ((len = bis.read(buffer, 0, 1024)) != -1) {
//                raf.write(buffer, 0, len);
//                downloadLength += len;
//            }
//            isCompleted = true;
//            Log.d(TAG, "current thread task has finished,all size:"
//                    + downloadLength);
//
//        } catch (IOException e) {
//            e.printStackTrace();
//        } finally {
//            if (bis != null) {
//                try {
//                    bis.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//            if (raf != null) {
//                try {
//                    raf.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//        }

        OkHttpClient client = new OkHttpClient();
        int retryingTimes = 0;
        boolean revisedInterval = false;

        do {
            // loop for retry
            Response response = null;
            long soFar = 0;
            final int id = 10000001;
            try {

                // Step 1, check state
//                if (checkState()) {
//                    if (FileDownloadLog.NEED_LOG) {
//                        FileDownloadLog.d(this, "already canceled %d %d", id, model.getStatus());
//                    }
//                    onPause();
//                    break;
//                }

//                if (FileDownloadLog.NEED_LOG) {
//                    FileDownloadLog.d(FileDownloadRunnable.class, "start download %s %s", id, model.getUrl());
//                }

                // Step 2, handle resume from breakpoint
//                checkIsResumeAvailable();

                Request.Builder requestBuilder = new Request.Builder().url(downloadUrl);
                addHeader(requestBuilder,startPos,endPos);
                requestBuilder.tag(id);
                // 目前没有指定cache，下载任务非普通REST请求，用户已经有了存储的地方
                requestBuilder.cacheControl(CacheControl.FORCE_NETWORK);

                // start download----------------
                // Step 3, init request
                final Request request = requestBuilder.get().build();
//                if (FileDownloadLog.NEED_LOG) {
//                    FileDownloadLog.d(this, "%s request header %s", id, request.headers());
//                }

                Call call = client.newCall(request);

                // Step 4, build connect
                response = call.execute();

                final boolean isSucceedStart = response.code() == HttpURLConnection.HTTP_OK;
                final boolean isSucceedResume = response.code() == HttpURLConnection.HTTP_PARTIAL;

//                if (isResumeDownloadAvailable && !isSucceedResume) {
//                    FileDownloadLog.w(this, "tried to resume from the break point[%d], but the " +
//                                    "response code is %d, not 206(PARTIAL).", model.getSoFar(),
//                            response.code());
//                }

                if (isSucceedStart || isSucceedResume) {
                    long total = totalSize;
                    final String transferEncoding = response.header("Transfer-Encoding");

                    // Step 5, check response's header
//                    if (isSucceedStart || total <= 0) {
//                        if (transferEncoding == null) {
//                            total = response.body().contentLength();
//                        } else {
//                            // if transfer not nil, ignore content-length
//                            total = TOTAL_VALUE_IN_CHUNKED_RESOURCE;
//                        }
//                    }

                    // TODO consider if not is chunked & http 1.0/(>=http1.1 & connect not be keep live) may not give content-length
                    if (total < 0) {
                        // invalid total length
                        final boolean isEncodingChunked = transferEncoding != null
                                && transferEncoding.equals("chunked");
                        if (!isEncodingChunked) {
                            // not chunked transfer encoding data
//                            if (FileDownloadProperties.getImpl().HTTP_LENIENT) {
//                                // do not response content-length either not chunk transfer encoding,
//                                // but HTTP lenient is true, so handle as the case of transfer encoding chunk
//                                total = TOTAL_VALUE_IN_CHUNKED_RESOURCE;
//                                if (FileDownloadLog.NEED_LOG) {
//                                    FileDownloadLog.d(this, "%d response header is not legal but " +
//                                            "HTTP lenient is true, so handle as the case of " +
//                                            "transfer encoding chunk", id);
//                                }
//                            } else {
//                                throw new FileDownloadGiveUpRetryException("can't know the size of the " +
//                                        "download file, and its Transfer-Encoding is not Chunked " +
//                                        "either.\nyou can ignore such exception by add " +
//                                        "http.lenient=true to the filedownloader.properties");
//                            }
                        }
                    }

//                    if (isSucceedResume) {
//                        soFar = model.getSoFar();
//                    }

                    // Step 6, callback on connected, and update header to db. for save etag.
//                    onConnected(isSucceedResume, total, findEtag(response), findFilename(response));

                    // Step 7, check whether has same task running after got filename from server/local generate.
//                    if (model.isPathAsDirectory()) {
//                        final int fileCaseId = FileDownloadUtils.generateId(model.getUrl(),
//                                model.getTargetFilePath());
//
//                        if (FileDownloadHelper.inspectAndInflowDownloaded(id,
//                                model.getTargetFilePath(), isForceReDownload, false)) {
//                            helper.remove(id);
//                            break;
//                        }
//
//                        final FileDownloadModel fileCaseModel = helper.find(fileCaseId);
//
//                        if (fileCaseModel != null) {
//                            if (FileDownloadHelper.inspectAndInflowDownloading(id, fileCaseModel,
//                                    threadPoolMonitor, false)) {
//                                helper.remove(id);
//                                break;
//                            }
//
//                            helper.remove(fileCaseId);
//                            deleteTargetFile();
//
//                            if (FileDownloadMgr.isBreakpointAvailable(fileCaseId, fileCaseModel)) {
//                                model.setSoFar(fileCaseModel.getSoFar());
//                                model.setTotal(fileCaseModel.getTotal());
//                                model.setETag(fileCaseModel.getETag());
//                                helper.update(model);
//                                // re connect to resume from breakpoint.
//                                continue;
//                            }
//                        }
//                    }

                    // Step 8, start fetch datum from input stream & write to file
                    if (fetch(response, isSucceedResume, soFar, total)) {
                        break;
                    }

                } else {
//                    final FileDownloadHttpException httpException =
//                            new FileDownloadHttpException(request, response);
//
//                    if (revisedInterval) {
//                        throw httpException;
//                    }
//                    revisedInterval = true;
//
//                    switch (response.code()) {
//                        case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
//                            deleteTaskFiles();
//                            FileDownloadLog.w(FileDownloadRunnable.class, "%d response code %d, " +
//                                            "range[%d] isn't make sense, so delete the dirty file[%s]" +
//                                            ", and try to redownload it from byte-0.", id,
//                                    response.code(), model.getSoFar(), model.getTempFilePath());
//                            onRetry(httpException, retryingTimes++);
//                            break;
//                        default:
//                            throw httpException;
//                    }
                }


            } catch (Throwable ex) {
//                // TODO 决策是否需要重试，是否是用户决定，或者根据错误码处理
//                if (autoRetryTimes > retryingTimes++
//                        && !(ex instanceof FileDownloadGiveUpRetryException)) {
//                    // retry
//                    onRetry(ex, retryingTimes);
//                } else {
//
//                    // error
//                    onError(ex);
//                    break;
//                }
            } finally {
                if (response != null && response.body() != null) {
                    response.body().close();
                }
            }

        } while (true);
    }

    /**
     * 线程文件是否下载完毕
     */
    public boolean isCompleted() {
        return isCompleted;
    }

    /**
     * 线程下载文件长度
     */
    public long getDownloadLength() {
        return downloadLength;
    }

    private void addHeader(Request.Builder builder,long startPos,long endPos) {
        final Headers additionHeaders;
//        if (header != null) {
//            if (FileDownloadProperties.getImpl().PROCESS_NON_SEPARATE) {
//                /**
//                 * In case of the FileDownloadService is not in separate process, the
//                 * function {@link FileDownloadHeader#writeToParcel} would never be invoked, so
//                 * {@link FileDownloadHeader#checkAndInitValues} would never be invoked.
//                 *
//                 * Instead, we can use {@link FileDownloadHeader#headerBuilder} directly.
//                 */
//                additionHeaders = header.getHeaders();
//            } else {
//                /**
//                 * In case of the FileDownloadService is in separate process to UI process,
//                 * the headers value would be carried by the parcel with
//                 * {@link FileDownloadHeader#checkAndInitValues()} .
//                 */
//                if (header.getNamesAndValues() != null) {
//                    additionHeaders = Headers.of(header.getNamesAndValues());
//                } else {
//                    additionHeaders = null;
//                }
//            }
//
//            if (additionHeaders != null) {
//                if (FileDownloadLog.NEED_LOG) {
//                    FileDownloadLog.v(this, "%d add outside header: %s", mId, additionHeaders);
//                }
//                builder.headers(additionHeaders);
//            }
//        }

//        if (isResumeDownloadAvailable) {
//            if (!TextUtils.isEmpty(model.getETag())) {
//                builder.addHeader("If-Match", model.getETag());
//            }
//            builder.addHeader("Range", formatString("bytes=%d-", sofar));
        builder.addHeader("Range", "bytes=" + startPos + "-" + endPos);
//        }
    }

    public static String formatString(final String msg, Object... args) {
        return String.format(Locale.ENGLISH, msg, args);
    }

    private boolean fetch(Response response, boolean isSucceedContinue,
                          long startPos, long total) throws Throwable {
        // fetching datum
        InputStream inputStream = null;
//        final FileDownloadOutputStream outputStream = getOutputStream(isSucceedContinue, total);
        RandomAccessFile raf = new RandomAccessFile(file, "rwd");
        //            byte[] buffer = new byte[1024];
//            bis = new BufferedInputStream(conn.getInputStream());
//
//            raf = new RandomAccessFile(file, "rwd");
            raf.seek(startPos);
//            int len;
//            while ((len = bis.read(buffer, 0, 1024)) != -1) {
//                raf.write(buffer, 0, len);
//                downloadLength += len;
//            }
//            isCompleted = true;


        try {
            // Step 1, get input stream
            inputStream = response.body().byteStream();
            byte[] buff = new byte[1024*64];

//            callbackMinIntervalBytes = calculateCallbackMinIntervalBytes(total, maxProgressCount);

//            long startWriteNanoTime = 0;
//            long currentNanoTime;
//            long latestWriteNanoTime = 0;
            // enter fetching loop(Step 2->6)
            do {

                // Step 2, read from input stream.
                int byteCount = inputStream.read(buff);
                if (byteCount == -1) {
                    break;
                }

//                if (FileDownloadLog.NEED_LOG) {
//                    startWriteNanoTime = System.nanoTime();
//                }
                // Step 3, writ to file
                raf.write(buff, 0, byteCount);
//                if (FileDownloadLog.NEED_LOG) {
//                    currentNanoTime = System.nanoTime();
//                    long writeConsume = currentNanoTime - startWriteNanoTime;
//                    if (latestWriteNanoTime != 0) {
//                        FileDownloadLog.v(this, "each fetch loop consume | write consume: | %fms | %fms",
//                                (currentNanoTime - latestWriteNanoTime - writeConsume) / 1000000f,
//                                writeConsume / 1000000f);
//                    }
//                    latestWriteNanoTime = currentNanoTime;
//                }

                downloadLength += byteCount;
//                // Step 4, adapter sofar
//                soFar += byteCount;
//
//                // Step 5, callback on progressing
//                onProgress(soFar, total, outputStream);
//
//                // Step 6, check state
//                if (checkState()) {
//                    // callback on paused
//                    onPause();
//                    return true;
//                }

            } while (true);

//            // Step 7, adapter chunked transfer encoding
//            if (total == TOTAL_VALUE_IN_CHUNKED_RESOURCE) {
//                total = soFar;
//            }

            // Step 8, Compare between the downloaded so far bytes with the total bytes.
//            if (soFar == total) {
//
//                // Step 9, rename the temp file to the completed file.
//                renameTempFile();
//
//                // Step 10, remove data from DB.
//                helper.remove(mId);
//
//                // callback completed
//                onComplete(total);
//
//                return true;
//            } else {
//                throw new RuntimeException(
//                        FileDownloadUtils.formatString("sofar[%d] not equal total[%d]", soFar, total));
//            }
        } finally {
            if (inputStream != null) {
                //noinspection ThrowFromFinallyBlock
                inputStream.close();
            }

            try {
//                if (outputStream != null) {
//                    //noinspection ThrowFromFinallyBlock
//                    outputStream.sync();
//                }
            } finally {
                //noinspection ConstantConditions
                if (inputStream != null) {
                    //noinspection ThrowFromFinallyBlock
                    inputStream.close();
                }
            }
        }
        return true;
    }
}
